package com.wontube.smr.bo.sites.youtube.impl;

import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringEscapeUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.wontube.client.extension.utils.net.URLUtils;
import com.wontube.core.pub.AppEnvironment;
import com.wontube.core.utils.net.SocketHttp;
import com.wontube.smr.bo.sites.youtube.IYouTubeService;
import com.wontube.smr.conf.LocalIPList;
import com.wontube.smr.model.vo.youtube.YouTubeVideoFile;
import com.wontube.smr.model.vo.youtube.YouTubeVideoType;





public final class HtmlYouTubeService implements IYouTubeService {
	private String htmlSource;
	private String pageUrl;
	private String videoTitle;
	private String cookie;
	private String errMsg;
	private final List<YouTubeVideoFile> formats = new ArrayList<YouTubeVideoFile>();

	private static final Logger logger = LoggerFactory.getLogger(HtmlYouTubeService.class);
	
	@Override
	public IYouTubeService createEmpty() {
		return new HtmlYouTubeService();
	}
	
	@Override
	public boolean obtain(String videoUrl)  {
		this.pageUrl = videoUrl;

		try {
			this.downloadHtmlSource();
			this.parseVideoTitle();
			this.parseJsonDownloadUrl();
			if (this.formats.size() < 1) {
				this.parseMapDownloadUrl();
			}
		} catch (Exception e) {
			this.errMsg = e.getMessage();
			return false;
		}

		return true;
	}

	/**
	 * @see com.wontube.smr.bo.sites.youtube.IYouTubeService#getPageUrl()
	 */
	@Override
	public String getPageUrl() {
		return this.pageUrl;
	}

	@Override
	public String getVideoTitle() {
		return this.videoTitle;
	}
	
	@Override
	public String getCookie() {
		return this.cookie;
	}

	@Override
	public YouTubeVideoFile[] getFormats() {
		return this.formats.toArray(new YouTubeVideoFile[0]);
	}

	@Override
	public YouTubeVideoFile[] getAvailableAudioFormat() {
		List<YouTubeVideoFile> list = new ArrayList<YouTubeVideoFile>();
		for (YouTubeVideoFile v : this.formats) {
			if (v.getVideoFormat() == YouTubeVideoType.Flash) {
				list.add(v);
			}
		}

		return list.toArray(new YouTubeVideoFile[0]);
	}
	
	@Override
	public String getErrMsg() {
		return this.errMsg;
	}

	private void downloadHtmlSource() {
		ArrayList<String> ipList = new ArrayList<String>(LocalIPList.IPList.getIPList());
		
		boolean success = false;
		Random random = new Random();
		while (ipList.size() > 0) {
			int index = random.nextInt(ipList.size());
			String localIP = ipList.get(index);
			ipList.remove(index);
			
			SocketHttp http = new SocketHttp(localIP);
			http.setTimeout(20000);

			InputStreamReader isr = null;
			try {
				logger.info("get youtube html source with ip: " + localIP);
				if (!http.get(this.pageUrl)) {
					throw new RuntimeException("Server error: " + http.getResponseMessage());
				}
				isr = new InputStreamReader(http.getInputStream(), AppEnvironment.charsetName);

				StringBuilder sb = new StringBuilder();
				char[] buffer = new char[1024];
				while (true) {
					int len = isr.read(buffer, 0, buffer.length);
					if (len <= 0) {
						break;
					}
					sb.append(buffer, 0, len);
				}

				this.htmlSource = sb.toString();
				success = this.checkHtmlSource();
				if (success || ipList.size() <= 0) {
					this.cookie = http.getHeaderValue("Set-Cookie");
					break;
				} else {
					if (logger.isInfoEnabled()) {
						logger.info("get htmlsource ({}) with ip {} failed, wait 1 s...", this.pageUrl, localIP);
					}
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
					}
				}			
			} catch (IOException e) {
				logger.error(e.toString());
				break;
			} finally {
				if (isr != null) {
					try {
						isr.close();
					} catch (IOException e) {
					}
				}
				http.close();
			}
		}

		if (!success) {
			throw new RuntimeException("Server is busy, Please wait a moment and try again.");
		}
	}

	private boolean checkHtmlSource() {
		return !this.htmlSource
				.contains("Sorry for the interruption. We have been receiving a large volume of requests from your network.");
	}

	private void parseVideoTitle() throws IOException {
		String videoTitlePattern = "<meta name=\"title\" content=\"(.*)\">";
		Pattern pattern = Pattern.compile(videoTitlePattern, Pattern.CASE_INSENSITIVE);
		Matcher matcher = pattern.matcher(this.htmlSource);

		if (matcher.find()) {
			String title = matcher.group(1);
			title = StringEscapeUtils.unescapeHtml4(title);
			title = title.replaceAll("[:*?%\"<>|]", "");
			title = title.replaceAll("[\\/]", "-");
			
			this.videoTitle = title;
		}
	}
	
	private void parseJsonDownloadUrl() throws IOException, URISyntaxException {
		String startConfig = "yt.playerConfig = ";
		int playerConfigIndex = this.htmlSource.indexOf(startConfig);

		if (playerConfigIndex >= 0) {
			int endOfJsonIndex = this.htmlSource.indexOf("};", playerConfigIndex + startConfig.length());
			String signature = this.htmlSource.substring(playerConfigIndex + startConfig.length(), endOfJsonIndex + "};".length() - 1);
			String code = "\"url_encoded_fmt_stream_map\":";
			int index = signature.indexOf(code) + code.length();
			String availableFormats = signature.substring(index).trim();
			index = availableFormats.indexOf("\",");
			availableFormats = availableFormats.substring(1, index);

			String argument = "url=";
			String endOfQueryString = "\\u0026";

			if (!"".equals(availableFormats)) {
				this.formats.clear();
				String[] urlList = availableFormats.split(argument);
				for (String url : urlList) {
					String u = url.trim();
					if (!"".equals(u)) {
						index = u.indexOf(endOfQueryString);
						u = u.substring(0, index);
						// 参数是二次编码的
						u = URLDecoder.decode(u, AppEnvironment.charsetName);
						u = URLDecoder.decode(u, AppEnvironment.charsetName);
						URL uri = new URL(u);
						Map<String, String> queryString = URLUtils.parseQueryString(uri.getQuery());
						String itag = queryString.get("itag");
						if (itag == null) {
							continue;
						}
						byte formatCode = Byte.parseByte(itag);
						this.formats.add(new YouTubeVideoFile(u, formatCode));
					}
				}
			}
		}
	}

	private void parseMapDownloadUrl() throws IOException, URISyntaxException {
		String startStreamMap = "url_encoded_fmt_stream_map=";
		int startStreamMapIndex = this.htmlSource.indexOf(startStreamMap);
		
		System.out.println(this.htmlSource);
		
		if (startStreamMapIndex >= 0) {
			int endOfJsonIndex = this.htmlSource.indexOf("&amp;", startStreamMapIndex + startStreamMap.length());
			String availableFormats = this.htmlSource.substring(startStreamMapIndex + startStreamMap.length(), endOfJsonIndex + "&amp;".length());
			String argument = "url%3D";
			String endOfQueryString = "%26";

			if (!"".equals(availableFormats)) {
				this.formats.clear();
				String[] urlList = availableFormats.split(argument);
				for (String url : urlList) {
					String u = url.trim();
					if (!"".equals(u)) {
						int index = u.indexOf(endOfQueryString);
						u = u.substring(0, index);
						// 参数是二次编码的
						u = URLDecoder.decode(u, AppEnvironment.charsetName);
						u = URLDecoder.decode(u, AppEnvironment.charsetName);
						URL uri = new URL(u);
						Map<String, String> queryString = URLUtils.parseQueryString(uri.getQuery());
						String itag = queryString.get("itag");
						if (itag == null) {
							continue;
						}
						
						byte formatCode = Byte.parseByte(itag);
						this.formats.add(new YouTubeVideoFile(u, formatCode));
					}
				}
			}
		}
	}
}
